/**
 * 
 */
package monitor;

import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author Robbie
 *
 */
public class PacketAnalyzer extends Thread implements PropertyChangeListener{
	
	private DatagramSocket mySocket;
	private ArrayList<DatagramPacket> packetQueue;
	private ArrayList<RemoteHost> nodeList;
	private PacketReceiver myReceiver;
	private Timer myTimer;
	private TimerTask myTask;
	private int myPortNum;
	
	private PropertyChangeSupport listeners = new PropertyChangeSupport(this);

	public PacketAnalyzer(int portNumber){
		myPortNum = portNumber;
		
		try {
			mySocket = new DatagramSocket(portNumber);
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		packetQueue = new ArrayList<DatagramPacket>();
		myReceiver = new PacketReceiver();
		myTimer = new Timer();
		nodeList = new ArrayList<RemoteHost>();
		setTimerTask();
	}
	
	private class PacketReceiver extends Thread{
		
		private boolean isInterrupted;
		private DatagramPacket myPacket;
		
		public PacketReceiver(){
			myPacket = new DatagramPacket(new byte[1500],1500);
		}
		
		public void run(){
			isInterrupted = false;
			while(!isInterrupted){
				try {
					mySocket.receive(myPacket);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				byte[] buffer = myPacket.getData();
				DatagramPacket temp = new DatagramPacket(buffer, buffer.length);
				temp.setAddress(myPacket.getAddress());
				temp.setPort(myPacket.getPort());
				myPacket = new DatagramPacket(new byte[1500],1500);
				synchronized(packetQueue){
					packetQueue.add(temp);
				}
			}
		}
		
		public void interruptData(){
			isInterrupted = true;
		}
	}
	
	public int getListenerPort(){
		return myPortNum;
	}
	
	public void run(){
//		System.out.println("PA started");
		myReceiver.start();
		
		myTimer.scheduleAtFixedRate(myTask, 1500, 1500);		
	}
	
	private void setTimerTask(){
		myTask = new TimerTask(){

			@Override
			public void run() {
//				System.out.println("Running...");
				ArrayList<DatagramPacket> packets = new ArrayList<DatagramPacket>();
				synchronized(packetQueue){
					packets.addAll(packetQueue);
					packetQueue = new ArrayList<DatagramPacket>();
				}
//				if(packets.size()<1){
//					return;
//				}
				ArrayList<String> uuidList = new ArrayList<String>();
				while(!packets.isEmpty()){
					DatagramPacket pkt = packets.remove(0);
					synchronized(pkt){
						byte[] buffer = pkt.getData();
//						System.out.println("PA received a packet: "+new String(buffer));
						String uuid = new String(buffer,0,36);
						uuidList.add(uuid);
						int index = 36;
						while(buffer[index]>32 && buffer[index]<123){
							String name = new String(buffer, index, 8);
							int length = Integer.parseInt(new String(buffer,index+8,4), 16);
							byte[] data = (new String(buffer,index+12,length)).getBytes();  //cheezey hack to get a range of bytes
							RemoteHost host = new RemoteHost(pkt.getAddress(), pkt.getPort(), uuid);
							PropertyChangeEvent e = new PropertyChangeEvent(this, name, host, data);
							scheduleUpdate(e);
							index += length+12;
						}
					}	//end of synchronized
					pkt = null;	
				}	//end of while loop
//				System.out.println("Node List size: "+ nodeList.size());
				for(int loop = 0; loop<nodeList.size(); loop++){
					nodeList.get(loop).increaseMessagesMissed();
//					System.out.println("Message Missed! " + nodeList.get(loop).getUUID().toString() + " : " + nodeList.get(loop).numberOfMissedMessages());
					if(nodeList.get(loop).numberOfMissedMessages()==5){
//						nodeList.get(loop).decreaseStatus();
						PropertyChangeEvent e = new PropertyChangeEvent(this, "STATUSDOWN", null, nodeList.get(loop).getUUID());
						scheduleUpdate(e);
					}else if(nodeList.get(loop).numberOfMissedMessages()==10){
//						nodeList.get(loop).decreaseStatus();
						PropertyChangeEvent e = new PropertyChangeEvent(this, "STATUSDOWN", null, nodeList.get(loop).getUUID());
						scheduleUpdate(e);
					}
				}
				while(!uuidList.isEmpty()){
					String uuid = uuidList.remove(0);
					uuidList.remove(uuid);
					for(int loop = 0; loop<nodeList.size();loop++){
						if(nodeList.get(loop).getUUID().toString().equals(uuid)){
							nodeList.get(loop).resetMissedMessages();
							PropertyChangeEvent e = new PropertyChangeEvent(this, "STATUSUP", null, nodeList.get(loop).getUUID());
							scheduleUpdate(e);
						}
					}
				}
			}	//end of run()
		};
	}
	
	/**
	 * Adds a property change listener
	 * 
	 * @param l listener to add
	 */
	public synchronized void addPropertyChangeListener(PropertyChangeListener l) {
	    listeners.addPropertyChangeListener(l);
	}
	
	/**
	 * Removes a property change listener
	 * 
	 * @param l listener to remove
	 */
	public synchronized void removePropertyChangeListener(PropertyChangeListener l) {
	    listeners.removePropertyChangeListener(l);
	}
	
	protected void fireUpdate(PropertyChangeEvent evt) {
	    listeners.firePropertyChange(evt);
	}


	protected void scheduleUpdate(final PropertyChangeEvent evt) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				fireUpdate(evt);
			}
		});
	}

	/* (non-Javadoc)
	 * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
	 */
	public void propertyChange(PropertyChangeEvent evt) {
		// TODO Auto-generated method stub
		if(evt.getPropertyName().equals("NEWNODE")){
			nodeList.add((RemoteHost) evt.getNewValue());
		}
	}

}
